package com.stox.ui.javafx.charting.view;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import javafx.application.Platform;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Orientation;
import javafx.scene.control.SplitPane;
import javafx.scene.input.DragEvent;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.BorderPane;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import com.stox.dto.AbstractModel;
import com.stox.dto.AbstractModel.Type;
import com.stox.dto.Scrip;
import com.stox.dto.enumeration.Span;
import com.stox.dto.enumeration.SpanType;
import com.stox.dto.intf.AbstractModelNavigable;
import com.stox.dto.preference.CorePreference;
import com.stox.dto.preference.PreferenceFactory;
import com.stox.ui.javafx.charting.draw.mode.DrawingMode;
import com.stox.ui.javafx.charting.event.handler.ChartingViewKeyEventHandler;
import com.stox.ui.javafx.charting.event.handler.ZoomEventHandler;
import com.stox.ui.javafx.charting.model.ChartingPreference;
import com.stox.ui.javafx.charting.model.cursor.StoxChartingCursor.CursorType;
import com.stox.ui.javafx.charting.model.plot.VolumePlot;
import com.stox.ui.javafx.charting.widget.Chart;
import com.stox.ui.javafx.charting.widget.ChartingContextMenu;
import com.stox.ui.javafx.charting.widget.ChartingToolBar;
import com.stox.ui.javafx.charting.widget.PrimaryChart;
import com.stox.ui.javafx.charting.widget.axis.VolumeAxis;
import com.stox.ui.javafx.core.util.ExceptionHandler.ExceptionHandlingRunnable;
import com.stox.ui.javafx.core.view.View;

@Component
@SuppressWarnings("rawtypes")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ChartingView extends View  {
	
	private static final List<ChartingView> chartingViews = new LinkedList<ChartingView>();
	
	private static final EventHandler<ActionEvent> viewClosedEventHandler = new EventHandler<ActionEvent>() {
		@Override
		public void handle(ActionEvent event) {
			chartingViews.remove(event.getTarget());
		}
	};
	
	private static final EventHandler<MouseEvent> viewSelectedEventHandler = new EventHandler<MouseEvent>(){
		@Override
		public void handle(MouseEvent event) {
			if(event.getSource() instanceof ChartingView){
				ChartingView chartingView = (ChartingView)event.getSource();
				chartingView.select();
			}
		}
	};
	
	public static ChartingView getSelectedChartingView() {
		for(ChartingView chartingView : chartingViews) {
			if(null != chartingView) {
				return chartingView;
			}
		}
		return null;
	}
	
	public static List<ChartingView> getChartingViews(){
		return chartingViews;
	}
	
	private int endIndex;
	
	private int startIndex;
	
    private double zoomStart = 0;
	
	private double zoomEnd = 1000;
	
    private VolumePlot volumePlot;
    
    private final VolumeAxis volumeAxis = new VolumeAxis(this);
    
    private final BorderPane borderPane = new BorderPane();
    
    private final SplitPane splitPane = new SplitPane();
	
	private final List<Chart> secondaryCharts = new ArrayList<>();
	
    private PrimaryChart primaryChart;
    
    private AbstractModelNavigable<? extends Collection<AbstractModel>> abstractModelNavigable;
    
    private CursorType cursorType = PreferenceFactory.get(ChartingPreference.class).getCursorType();
    
    private Date lastTreadingDate = PreferenceFactory.get(CorePreference.class).getLastTreadingDate();
    
    /**
     * Drawing modes for shapes
     */
    private ObjectProperty<DrawingMode> drawingMode;
    public ObjectProperty<DrawingMode> drawingModeProperty(){
    	if(null == drawingMode){
    		drawingMode = new SimpleObjectProperty<DrawingMode>();
    	}
    	return drawingMode;
    }
    public void setDrawingMode(DrawingMode drawingMode){
    	drawingModeProperty().set(drawingMode);
    }
    public DrawingMode getDrawingMode(){
    	return null == drawingMode ? null : drawingMode.get();
    }
    
	@Autowired
	private ChartingViewDisplayHelper chartingViewDisplayHelper;
	
    private EventHandler<DragEvent> onDragOverEventHandler = new EventHandler<DragEvent>() {
		@Override
		public void handle(DragEvent event) {
			if(event.getGestureSource() instanceof AbstractModelNavigable){
				event.acceptTransferModes(TransferMode.ANY);
				event.consume();
			}
		}
    };
    
    private EventHandler<DragEvent> onDragDroppedEventHandler = new EventHandler<DragEvent>(){
		
    	private Collection<AbstractModel> getModelsOfType(Collection<AbstractModel> models, Type type){
    		Collection<AbstractModel> result = new ArrayList<AbstractModel>();
    		for(AbstractModel abstractModel : models){
    			if(abstractModel.getType().isSubTypeOf(type)){
    				result.add(abstractModel);
    			}
    		}
    		return result;
    	}
    	
		@Override
		@SuppressWarnings("unchecked")
		public void handle(DragEvent event) {
			if(event.getGestureSource() instanceof AbstractModelNavigable){
				select();
				AbstractModelNavigable<? extends Collection<AbstractModel>> abstractModelNavigable = (AbstractModelNavigable)event.getSource();
				Collection<AbstractModel> models = abstractModelNavigable.get();
				models = getModelsOfType(models, Scrip.ScripType.ANY);
				if(!models.isEmpty()){
					chartingViewDisplayHelper.displaySingle((AbstractModelNavigable<? extends Collection<AbstractModel>>)event.getGestureSource());
				}
				event.setDropCompleted(true);
				event.consume();
			}
		}
    };
    
    public ChartingView() {
    	getStyleClass().add("charting-view");
    	splitPane.setOrientation(Orientation.VERTICAL);
    	borderPane.setCenter(splitPane);
		getContentPane().getChildren().add(borderPane);
		
		setOnClosedAction(viewClosedEventHandler);
		addEventFilter(MouseEvent.MOUSE_PRESSED, viewSelectedEventHandler);
		addEventFilter(MouseEvent.MOUSE_MOVED, cursorType.getCursor());
		addEventFilter(MouseEvent.MOUSE_DRAGGED, cursorType.getCursor());
		setOnDragOver(onDragOverEventHandler);
    	setOnDragDropped(onDragDroppedEventHandler);
		drawingModeProperty().addListener(new ChangeListener<DrawingMode>(){
			@Override
			public void changed(
					ObservableValue<? extends DrawingMode> observable,
					DrawingMode oldValue, DrawingMode newValue) {
				if(null != oldValue){
					oldValue.detach(getPrimaryChart().getCanvas());
					for(Chart chart : getSecondaryCharts()){
						oldValue.detach(chart.getCanvas());
					}
				}
				if(null != newValue){
					newValue.attach(getPrimaryChart().getCanvas());
					for(Chart chart : getSecondaryCharts()){
						newValue.attach(chart.getCanvas());
					}
				}
			}
		});
    	
    	select();
	}
    
    public void select(){
    	chartingViews.remove(this);
    	chartingViews.add(0,this);
    	requestFocus();
    }
    
    @Autowired
	public void setChartingContextMenu(final ChartingContextMenu chartingContextMenu){
    	addEventFilter(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>(){
			@Override
			public void handle(MouseEvent event) {
				if(MouseButton.SECONDARY.equals(event.getButton())){
					chartingContextMenu.show((ChartingView)event.getSource(), event.getSceneX(), event.getSceneY());
				}else{
					if(chartingContextMenu.isShowing()){
						chartingContextMenu.hide();
					}
				}
			}
		});
	}
    
    @Autowired
    private void setChartingViewKeyEventHandler(ChartingViewKeyEventHandler chartingViewKeyEventHandler){
    	addEventFilter(KeyEvent.KEY_PRESSED, chartingViewKeyEventHandler);
    }
	
	@Autowired
	public void setZoomEventHandler(ZoomEventHandler zoomEventHandler){
		setOnScroll(zoomEventHandler);
	}
	
	@Autowired
	public void setApplicationContext(ApplicationContext applicationContext){
		this.primaryChart = (PrimaryChart)applicationContext.getBean("primaryChart",this);
		splitPane.getItems().add(primaryChart);
		borderPane.setTop(new ChartingToolBar(this, applicationContext));
	}
	
	
	private boolean loading = false;
	
	public void update() {
		
		Platform.runLater(new ExceptionHandlingRunnable(){
			@Override
			public void doRun() {
				if(loading || primaryChart == null || primaryChart.getFrontPlot() == null) return;

					updateZoom();
					
					primaryChart.update();
					
					for(Chart chart : secondaryCharts) 
						chart.update();
					
					primaryChart.getFrontPlot().setDataChanged(false);
					
					paint();
				}
		});
		
	}

	public void paint() {
		if(loading || primaryChart == null || primaryChart.getFrontPlot() == null) return;
		
		primaryChart.paint();
		
		for(Chart chart : secondaryCharts) 
			chart.paint();
		spanTypeDirty = false;
	}
	
	
	private SpanType spanType = PreferenceFactory.get(ChartingPreference.class).getSpanType();
	private Span span = PreferenceFactory.get(ChartingPreference.class).getSpan();
	private boolean autoSpan = PreferenceFactory.get(ChartingPreference.class).isAutoSpan();
	private boolean autoSpanType = PreferenceFactory.get(ChartingPreference.class).isAutoSpanType();
	private int defaultBars = PreferenceFactory.get(ChartingPreference.class).getDefaultNumberOfBars();
	private int autoFetchSize =PreferenceFactory.get(ChartingPreference.class).getAutoFetchSize();
	private int minBars = PreferenceFactory.get(ChartingPreference.class).getMinimumNumberOfBars();
	private int maxBars = PreferenceFactory.get(ChartingPreference.class).getMaximumNumberOfBars();
	
	private boolean spanTypeDirty = false;
	
	public void updateZoom() {
		int size = primaryChart.getFrontPlot().getData().size();
		startIndex = (int)(zoomStart*size/1000);
		endIndex = (int)zoomEnd*size/1000;
		
		if(autoSpanType){
			if((endIndex - startIndex) < minBars){
				SpanType spanType = this.spanType.down();
				if(!spanTypeDirty && SpanType.DAILY.ordinal() <= spanType.ordinal()){
					this.spanType = spanType;
					spanTypeDirty = true;
				}
			}
			if((endIndex - startIndex) > maxBars){
				SpanType spanType = this.spanType.up();
				if(!spanTypeDirty && null != spanType){
					this.spanType = spanType;
					spanTypeDirty = true;
				}
			}
		}
	}
	
	public VolumePlot getVolumePlot() {
		return volumePlot;
	}
	
	public void setVolumePlot(VolumePlot volumePlot) {
		this.volumePlot = volumePlot;
	}
	
	public void setZoom(double low, double high) {
		if(low >= high) return;
		this.zoomStart = 1000 - high;
		this.zoomEnd = 1000 - low;
		updateZoom();
	}

	public double getZoomStart() {
		return zoomStart;
	}
	
	public double getZoomEnd() {
		return zoomEnd;
	}
	
	public PrimaryChart getPrimaryChart() {
		return primaryChart;
	}

	public int getStartIndex() {
		return startIndex;
	}
	
	public void setStartIndex(int startIndex) {
		this.startIndex = startIndex;
	}

	public int getEndIndex() {
		return endIndex;
	}
	
	public void setEndIndex(int endIndex) {
		this.endIndex = endIndex;
	}

	public List<Chart> getSecondaryCharts() {
		return Collections.unmodifiableList(secondaryCharts);
	}
	
	public void addSecondaryChart(Chart secondaryChart) {
		secondaryCharts.add(secondaryChart);
		splitPane.getItems().add(secondaryChart);
		int size = secondaryCharts.size();
		for(int index = 0; index <  size; index++){
			splitPane.setDividerPosition(index, 1- ((size - index)*0.2));
		}
	}
	
	public void removeSecondaryChart(Chart secondaryChart) {
		secondaryCharts.remove(secondaryChart);
		splitPane.getItems().remove(secondaryChart);
		int size = secondaryCharts.size();
		for(int index = 0; index <  size; index++){
			splitPane.setDividerPosition(index, 1- ((size - index)*0.2));
		}
	}
	
	public void setZoomStart(double zoomStart) {
		this.zoomStart = zoomStart;
	}

	public void setZoomEnd(double zoomEnd) {
		this.zoomEnd = zoomEnd;
	}

	public AbstractModelNavigable<? extends Collection<AbstractModel>> getAbstractModelNavigable() {
		return abstractModelNavigable;
	}

	public void setAbstractModelNavigable(
			AbstractModelNavigable<? extends Collection<AbstractModel>> abstractModelNavigable) {
		this.abstractModelNavigable = abstractModelNavigable;
	}

	public int getDefaultBars() {
		return defaultBars;
	}

	public void setDefaultBars(int defaultBars) {
		this.defaultBars = defaultBars;
	}

	public int getMinBars() {
		return minBars;
	}

	public void setMinBars(int minBars) {
		this.minBars = minBars;
	}

	public int getMaxBars() {
		return maxBars;
	}

	public void setMaxBars(int maxBars) {
		this.maxBars = maxBars;
	}

	public int getAutoFetchSize() {
		return autoFetchSize;
	}

	public void setAutoFetchSize(int autoFetchSize) {
		this.autoFetchSize = autoFetchSize;
	}

	public SpanType getSpanType() {
		return spanType;
	}

	public void setSpanType(SpanType spanType) {
		this.spanType = spanType;
	}

	public CursorType getCursorType() {
		return cursorType;
	}

	public void setCursorType(CursorType cursorType) {
		this.cursorType = cursorType;
	}

	public VolumeAxis getVolumeAxis() {
		return volumeAxis;
	}

	public Span getSpan() {
		return span;
	}

	public void setSpan(Span span) {
		this.span = span;
	}

	public Date getLastTreadingDate() {
		return lastTreadingDate;
	}

	public void setLastTreadingDate(Date lastTreadingDate) {
		this.lastTreadingDate = lastTreadingDate;
	}

	public boolean isAutoSpan() {
		return autoSpan;
	}

	public void setAutoSpan(boolean autoSpan) {
		this.autoSpan = autoSpan;
	}

	public boolean isAutoSpanType() {
		return autoSpanType;
	}

	public void setAutoSpanType(boolean autoSpanType) {
		this.autoSpanType = autoSpanType;
	}

	public boolean isLoading() {
		return loading;
	}

	public void setLoading(boolean loading) {
		this.loading = loading;
	}
}
